home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2007 September / PCWSEP07.iso / Software / Linux / Linux Mint 3.0 Light / LinuxMint-3.0-Light.iso / casper / filesystem.squashfs / usr / lib / hplip / pcard / photocard.py < prev    next >
Encoding:
Python Source  |  2007-04-04  |  20.7 KB  |  733 lines

  1. # -*- coding: utf-8 -*-
  2. #
  3. # (c) Copyright 2003-2007 Hewlett-Packard Development Company, L.P.
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
  18. #
  19.  
  20. # Std Lib
  21. import struct
  22. import time
  23. import fnmatch
  24. import mimetypes
  25. import array
  26.  
  27. # Local
  28. from base.g import *
  29. from base.codes import *
  30. from base import device, utils, exif
  31.  
  32. # Extensions
  33. import pcardext
  34.  
  35. # Photocard command codes
  36. ACK = 0x0100
  37. NAK = 0x0101
  38. READ_CMD = 0x0010
  39. WRITE_CMD = 0x0020
  40.  
  41. SECTOR_SIZE = 512 # don't change this (TODO: impl. in pcardext)
  42.  
  43. # Photocard sector cache
  44. MAX_CACHE = 512 # units = no. sectors 
  45.  
  46. # PhotoCardFile byte cache
  47. # Used for thumbnails
  48. INITIAL_PCARDFILE_BUFFER = 20*SECTOR_SIZE 
  49. INCREMENTAL_PCARDFILE_BUFFER = 2*SECTOR_SIZE 
  50.  
  51. class PhotoCardFile:    
  52.     # File-like interface
  53.  
  54.     def __init__(self, pc, name=None):
  55.         self.pos = 0
  56.         self.closed = True
  57.         self.file_size = 0
  58.         self.pc = pc
  59.         self.buffer = array.array('c') 
  60.  
  61.         if name is not None:
  62.             self.open(name)
  63.  
  64.         self.buffer_size = INITIAL_PCARDFILE_BUFFER
  65.         self.buffer.fromstring(pcardext.read(self.name, 0, self.buffer_size))
  66.  
  67.  
  68.     def open(self, name):
  69.         self.closed = False
  70.         self.name = name
  71.  
  72.     def seek(self, offset, whence=0):
  73.         if whence == 0:
  74.             self.pos = offset
  75.         elif whence == 1:
  76.             self.pos += offset
  77.         elif whence == 2:
  78.             self.pos = self.file_size - offset
  79.         else:
  80.             return
  81.  
  82.  
  83.     def tell(self):
  84.         return self.pos
  85.  
  86.  
  87.     def read(self, size): 
  88.         if size > 0:
  89.             if self.pos + size < self.buffer_size:
  90.                 data = self.buffer[self.pos : self.pos + size].tostring()
  91.                 self.pos += size
  92.                 return data
  93.             else:
  94.                 # Read some more in from the card to satisfy the request
  95.                 while self.pos + size >= self.buffer_size:
  96.                     self.buffer.fromstring(pcardext.read(self.name, self.buffer_size, INCREMENTAL_PCARDFILE_BUFFER))
  97.                     self.buffer_size += INCREMENTAL_PCARDFILE_BUFFER
  98.                 return self.read(size)
  99.  
  100.  
  101.     def close(self):
  102.         self.closed = True
  103.         self.pos = 0
  104.  
  105.  
  106. class PhotoCard:
  107.  
  108.     def __init__(self, dev_obj=None, device_uri=None, printer_name=None):
  109.  
  110.         if dev_obj is None:
  111.             self.device = device.Device(device_uri, printer_name)
  112.             self.device.open()
  113.             self.close_device = True
  114.         else:
  115.             self.device = dev_obj
  116.             self.close_device = False
  117.  
  118.         self.dir_stack = utils.Stack()
  119.         self.current_dir = []
  120.         self.device_uri = self.device.device_uri
  121.         self.pcard_mounted = False
  122.         self.saved_pwd = []
  123.         self.sector_buffer = {}
  124.         self.sector_buffer_counts = {}
  125.         self.cache_flag = True
  126.         self.write_protect = False
  127.  
  128.         self.callback = None
  129.  
  130.         self.channel_opened = False
  131.  
  132.  
  133.     def START_OPERATION(self, name=''):
  134.         pass
  135.  
  136.     def END_OPERATION(self, name='', flag=True):
  137.         if self.channel_opened and flag:
  138.             self.close_channel()
  139.  
  140.     def set_callback(self, callback):
  141.         self.callback = callback
  142.  
  143.     def _read(self, sector, nsector): 
  144.         log.debug("read pcard sector: sector=%d count=%d" % (sector, nsector))
  145.  
  146.         if self.cache_flag:
  147.             for s in range(sector, sector+nsector):
  148.                 if s not in self.sector_buffer:
  149.                     break
  150.             else:
  151.                 buffer = ''
  152.                 for s in range(sector, sector+nsector):
  153.                     buffer = ''.join([buffer, self.sector_buffer[s]])
  154.                     log.debug("Cached sector read sector=%d" % s)
  155.                     count = self.sector_buffer_counts[s]
  156.                     self.sector_buffer_counts[s] = count+1
  157.  
  158.                     if self.callback is not None:
  159.                         self.callback()
  160.  
  161.                 return buffer
  162.  
  163.         if self.callback is not None:
  164.             self.callback()
  165.  
  166.         if not self.channel_opened:
  167.             self.open_channel()
  168.  
  169.         log.debug("Normal sector read sector=%d count=%d" % (sector, nsector))
  170.         sectors_to_read = range(sector, sector+nsector)
  171.         request = struct.pack('!HH' + 'I'*nsector, READ_CMD, nsector, *sectors_to_read)
  172.  
  173.         if self.callback is not None:
  174.             self.callback()
  175.  
  176.         # send out request
  177.         bytes_written = self.device.writePCard(request)
  178.         log.debug("%d bytes written" % bytes_written)
  179.  
  180.         # read return code
  181.         data = self.device.readPCard(2)
  182.         code = struct.unpack('!H', data)[0]
  183.  
  184.         log.debug("Return code: %x" % code)
  185.  
  186.         if code == 0x0110:
  187.  
  188.             # read sector count and version
  189.             data = self.device.readPCard(6)
  190.             nsector_read, ver = struct.unpack('!IH', data)
  191.  
  192.             log.debug("code=0x%x, nsector=%d, ver=%d" % (code, nsector_read, ver))
  193.  
  194.             buffer, data_read, total_to_read = '', 0, nsector * SECTOR_SIZE
  195.  
  196.             while (data_read < total_to_read):
  197.                 data = self.device.readPCard(total_to_read)
  198.  
  199.                 data_read += len(data)
  200.                 buffer = ''.join([buffer, data])
  201.  
  202.                 if self.callback is not None:
  203.                     self.callback()            
  204.  
  205.             if self.cache_flag:
  206.                 i = 0
  207.  
  208.                 for s in range(sector, sector + nsector_read):
  209.                     self.sector_buffer[s] = buffer[i : i+SECTOR_SIZE]
  210.                     log.debug("Sector %d data=\n%s" % (s, repr(self.sector_buffer[s])))
  211.                     count = self.sector_buffer_counts.get(s, 0)
  212.                     self.sector_buffer_counts[s] = count+1
  213.                     i += SECTOR_SIZE
  214.  
  215.                     if self.callback is not None:
  216.                         self.callback()            
  217.  
  218.                 self._check_cache(nsector)
  219.  
  220.             return buffer
  221.         else:
  222.             log.error("Error code: %d" % code)
  223.             return ''
  224.  
  225.     def _write(self, sector, nsector, buffer):
  226.  
  227.         log.debug("write pcard sector: sector=%d count=%d len=%d data=\n%s" % (sector, nsector, len(buffer), repr(buffer)))
  228.  
  229.         if not self.channel_opened:
  230.             self.open_channel()
  231.  
  232.  
  233.         sectors_to_write = range(sector, sector+nsector)
  234.         request = struct.pack('!HHH' + 'I'*nsector, WRITE_CMD, nsector, 0, *sectors_to_write)
  235.         request = ''.join([request, buffer])
  236.  
  237.         if self.callback is not None:
  238.             self.callback()
  239.  
  240.         self.device.writePCard(request)
  241.         data = self.device.readPCard(2)
  242.  
  243.         if self.callback is not None:
  244.             self.callback()
  245.  
  246.         code = struct.unpack('!H', data)[0]
  247.  
  248.         if code != NAK:
  249.             if self.cache_flag:
  250.                 i = 0
  251.                 for s in range(sector, sector+nsector):
  252.                     log.debug("Caching sector %d" % sector)
  253.                     self.sector_buffer[s] = buffer[i:i+SECTOR_SIZE]
  254.                     self.sector_buffer_counts[s] = 1
  255.                     i += SECTOR_SIZE
  256.  
  257.                 if self.callback is not None:
  258.                     self.callback()    
  259.  
  260.                 self._check_cache(nsector)
  261.  
  262.             return 0
  263.  
  264.         else:    
  265.             if self.cache_flag:
  266.                 for s in range(sector, sector+nsector):
  267.                     try:
  268.                         del self.sector_buffer[s]
  269.                         del self.sector_buffer_counts[s]
  270.                     except KeyError:
  271.                         pass
  272.  
  273.             log.error("Photo card write failed (Card may be write protected)")
  274.             self.close_channel()
  275.             return 1
  276.  
  277.  
  278.     def _check_cache(self, nsector):
  279.         if len(self.sector_buffer) > MAX_CACHE:
  280.             # simple minded: scan for first nsector sectors that has count of 1 and throw it away
  281.             t, n = self.sector_buffer.keys()[:], 0
  282.             for s in t:
  283.                 if self.sector_buffer_counts[s] == 1:
  284.                     del self.sector_buffer[s]
  285.                     del self.sector_buffer_counts[s]
  286.                     n += 1
  287.                     if n >= nsector:
  288.                         break
  289.                     if self.callback is not None:
  290.                         self.callback()
  291.  
  292.  
  293.  
  294.     def cache_info(self):
  295.         return self.sector_buffer_counts
  296.  
  297.     def cache_check(self, sector):
  298.         return self.sector_buffer_counts.get(sector, 0)
  299.  
  300.     def cache_control(self, control):
  301.         self.cache_flag = control
  302.  
  303.         if not self.cache_flag:
  304.             self.cache_reset()
  305.  
  306.     def cache_state(self):
  307.         return self.cache_flag
  308.  
  309.     def cache_reset(self):
  310.         self.sector_buffer.clear()
  311.         self.sector_buffer_counts.clear()
  312.  
  313.     def df(self):
  314.         df = 0
  315.         self.START_OPERATION('df')
  316.         try:
  317.             df = pcardext.df()
  318.         finally:
  319.             self.END_OPERATION('df')
  320.             return df
  321.  
  322.     def ls(self, force_read=True, glob_list='*', openclose=True):
  323.         if not glob_list:
  324.             glob_list = '*'
  325.         if force_read:
  326.             self.START_OPERATION('ls')
  327.             try:
  328.                 self.current_dir = pcardext.ls()
  329.             finally:
  330.                 self.END_OPERATION('ls', openclose)
  331.  
  332.         self.current_dir = [(n.lower(),a,s) for (n,a,s) in self.current_dir]
  333.  
  334.         if glob_list == '*':
  335.             return self.current_dir
  336.  
  337.         return [fnmatch.filter(self.current_dir, x) for x in glob_list.strip().lower().split()][0]
  338.  
  339.     def size(self, name):
  340.         for f in self.current_dir:
  341.             if f == name:
  342.                 return self.current_dir[f][2]
  343.         return 0
  344.  
  345.     def current_files(self):
  346.         return [x for x in self.current_dir if x[1] != 'd']
  347.  
  348.     def current_directories(self):
  349.         return [x for x in self.current_dir if x[1] == 'd']
  350.  
  351.     def match_files(self, glob_list):
  352.         if len(glob_list) > 0:
  353.             current_files = [x[0] for x in self.current_files()]
  354.             return [fnmatch.filter(current_files, x) for x in glob_list.strip().lower().split()][0]
  355.         return []
  356.  
  357.     def match_dirs(self, glob_list):
  358.         if len(glob_list) > 0:
  359.             current_dirs = [x[0] for x in self.current_directories()]
  360.             return [fnmatch.filter(current_dirs, x) for x in glob_list.strip().lower().split()][0]
  361.         return []
  362.  
  363.     def classify_file(self, filename):
  364.         t = mimetypes.guess_type(filename)[0]
  365.         if t is None:
  366.             return 'unknown/unknown'
  367.         return t
  368.  
  369.     # copy a single file fom pwd to lpwd
  370.     def cp(self, name, local_file, openclose=True):
  371.         self.START_OPERATION('cp')
  372.         total = 0
  373.         try:
  374.             f = file(local_file, 'w');
  375.             total = pcardext.cp(name, f.fileno())
  376.             f.close()
  377.         finally:
  378.             self.END_OPERATION('cp', openclose)
  379.             return total
  380.  
  381.     # cp multiple files in the current working directory
  382.     def cp_multiple(self, filelist, remove_after_copy, cp_status_callback=None, rm_status_callback=None):
  383.         delta, total = 0, 0
  384.         self.START_OPERATION('cp_multiple')
  385.         t1 = time.time()
  386.         try:
  387.             for f in filelist:
  388.  
  389.                 size = self.cp(f, f, False)
  390.  
  391.                 if cp_status_callback:
  392.                     cp_status_callback(os.path.join(self.pwd(), f), os.path.join(os.getcwd(), f), size)
  393.  
  394.                 total += size
  395.  
  396.  
  397.                 if remove_after_copy:
  398.                     pcardext.rm(f)
  399.  
  400.             t2 = time.time()
  401.             delta = t2-t1
  402.         finally:
  403.             if remove_after_copy:
  404.                 self.ls(True, '*', False)
  405.             self.END_OPERATION('cp_multiple')
  406.             return (total, delta)
  407.  
  408.     # cp multiple files with paths
  409.     def cp_list(self, filelist, remove_after_copy, cp_status_callback=None, rm_status_callback=None):
  410.         self.save_wd()
  411.         delta, total = 0, 0
  412.         self.START_OPERATION('cp_list')
  413.         t1 = time.time()
  414.         try:
  415.             for f in filelist:
  416.  
  417.                 path_list = f.split('/')[:-1]
  418.                 filename = f.split('/')[-1]
  419.  
  420.                 for p in path_list:
  421.                     self.cd(p, False)
  422.  
  423.                 size = self.cp(filename, filename, False)
  424.  
  425.                 if cp_status_callback is not None:
  426.                     cp_status_callback(f, os.path.join(os.getcwd(), filename), size)
  427.  
  428.                 total += size    
  429.  
  430.                 if remove_after_copy:
  431.                     pcardext.rm(filename)
  432.  
  433.                     if rm_status_callback is not None:
  434.                         rm_status_callback(f)
  435.  
  436.                 self.cd('/', False)
  437.  
  438.             t2 = time.time()
  439.             delta = t2-t1
  440.         finally:
  441.             #if remove_after_copy:
  442.             #    self.ls( True, '*', False )
  443.             self.restore_wd()
  444.             self.END_OPERATION('cp_list')
  445.             return (total, delta)
  446.  
  447.  
  448.  
  449.     def cp_fd(self, name, fd):
  450.         total = 0
  451.         self.START_OPERATION('cp_fd')
  452.         try:
  453.             total = pcardext.cp(name, fd)
  454.         finally:
  455.             self.END_OPERATION('cp_fd')
  456.             return total
  457.  
  458.  
  459.     def unload(self, unload_list, cp_status_callback=None, rm_status_callback=None, dont_remove=False):
  460.         was_cancelled = False
  461.         self.save_wd()
  462.         self.START_OPERATION('unload')
  463.         total = 0
  464.         t1 = time.time()
  465.  
  466.         for f in unload_list:
  467.             if not was_cancelled:
  468.                 name, size, typ, subtyp = f
  469.  
  470.                 p = name.split('/')
  471.                 dirs = p[:-1]
  472.                 filename = p[-1]
  473.                 self.cd('/', False)
  474.  
  475.                 if cp_status_callback is not None:
  476.                     if cp_status_callback(os.path.join(self.pwd(), filename), 
  477.                                             os.path.join(os.getcwd(), filename), 1):
  478.                         was_cancelled = True
  479.                         break
  480.  
  481.                 if len(dirs) > 0:
  482.                     for d in dirs:
  483.                         self.cd(d, False)
  484.  
  485.                 if os.path.exists(os.path.join(os.getcwd(), filename)):
  486.                     i = 2
  487.  
  488.                     while True:
  489.                         if not os.path.exists(os.path.join(os.getcwd(), filename + " (%d)" % i)):
  490.                             break
  491.  
  492.                         i += 1
  493.  
  494.                     total += self.cp(filename, filename + " (%d)" % i, False)
  495.  
  496.                 else:    
  497.                     total += self.cp(filename, filename, False)
  498.  
  499.                 if cp_status_callback is not None:
  500.                     if cp_status_callback(os.path.join(self.pwd(), filename), 
  501.                                             os.path.join(os.getcwd(), filename), size):
  502.                         was_cancelled = True
  503.                         break
  504.  
  505.                 if not dont_remove:
  506.                     if rm_status_callback is not None:
  507.                         rm_status_callback(os.path.join(self.pwd(), filename))
  508.  
  509.                     self.rm(filename, False, False)
  510.  
  511.  
  512.         t2 = time.time()
  513.         self.restore_wd(False)
  514.         self.ls(True, '*', False)
  515.         self.END_OPERATION('unload')
  516.  
  517.         return total, (t2-t1), was_cancelled
  518.  
  519.  
  520.     def get_unload_list(self):
  521.         tree = self.tree()
  522.         return self.__build_unload_list(tree)
  523.  
  524.  
  525.     def __build_unload_list(self, tree, path=None, out=None): 
  526.         if path is None:
  527.             out = []
  528.             path = utils.Stack()
  529.         for d in tree:
  530.             if type(tree[d]) == type({}):
  531.                 path.push(d)
  532.                 self.__build_unload_list(tree[d], path, out) 
  533.                 path.pop()
  534.             else:
  535.                 typ, subtyp = self.classify_file(d).split('/')
  536.                 if typ in ['image', 'audio', 'video']:
  537.                     p = path.as_list()
  538.                     name = '/'.join(['/'.join(p), d])
  539.                     out.append((name, tree[d], typ, subtyp)) 
  540.  
  541.         return out
  542.  
  543.  
  544.     def info(self):
  545.         return pcardext.info()
  546.  
  547.  
  548.     def cd(self, dirs, openclose=True):
  549.         self.START_OPERATION('cd')
  550.         try:
  551.             stat = pcardext.cd(dirs)
  552.             if stat:
  553.                 if dirs == '/':
  554.                     self.dir_stack.clear()
  555.  
  556.                 else:
  557.                     dirs = dirs.split('/')
  558.                     for d in dirs:
  559.                         self.dir_stack.push(d)
  560.  
  561.                 self.ls(True, '*', False)
  562.  
  563.         finally:
  564.             self.END_OPERATION('cd', openclose)
  565.  
  566.  
  567.     def cdup(self, openclose=True):
  568.         if len(self.dir_stack.as_list()) == 0:
  569.             return self.cd('/', openclose)
  570.  
  571.         self.dir_stack.pop()
  572.         self.START_OPERATION('cdup')
  573.         try:
  574.             pcardext.cd('/')
  575.  
  576.             for d in self.dir_stack.as_list():
  577.                 pcardext.cd(d)
  578.  
  579.             self.ls(True, '*', False)
  580.         finally:
  581.             self.END_OPERATION('cdup', openclose)
  582.  
  583.     def rm(self, name, refresh_dir=True, openclose=True):
  584.         self.START_OPERATION()
  585.         try:
  586.             r = pcardext.rm(name)
  587.  
  588.             if refresh_dir:
  589.                 self.ls(True, '*', False)
  590.         finally:
  591.             self.END_OPERATION(openclose)
  592.             return r
  593.  
  594.     def mount(self):
  595.         log.debug("Mounting photocard...")
  596.         self.START_OPERATION('mount')
  597.         try:
  598.             stat = pcardext.mount(self._read, self._write)
  599.             disk_info = pcardext.info()
  600.             self.write_protect = disk_info[8]
  601.             log.debug("stat=%d" % stat)
  602.  
  603.             if stat == 0:
  604.                 if self.write_protect:
  605.                     # if write_protect is True,
  606.                     # card write NAK'd and channel was 
  607.                     # closed. We have to reopen here.
  608.                     self.open_channel()
  609.  
  610.                 self.pcard_mounted = True
  611.                 pcardext.cd('/')
  612.  
  613.                 self.ls(True, '*', False)
  614.  
  615.             else:
  616.                 self.pcard_mounted = False
  617.                 raise Error(ERROR_DEVICE_DOES_NOT_SUPPORT_OPERATION)
  618.         finally:
  619.             if self.pcard_mounted:
  620.                 self.END_OPERATION('mount')
  621.  
  622.  
  623.  
  624.     def pwd(self):
  625.         return '/' + '/'.join(self.dir_stack.as_list())
  626.  
  627.  
  628.     def save_wd(self):
  629.         self.saved_pwd = self.dir_stack.as_list()[:]
  630.  
  631.     def restore_wd(self, openclose=True):
  632.         self.cd('/', openclose)
  633.         for d in self.saved_pwd:
  634.             self.cd(d, openclose)
  635.  
  636.  
  637.     def tree(self):
  638.         self.START_OPERATION('tree')
  639.         dir_tree = {}
  640.         try:
  641.             self.save_wd()
  642.             dir_tree = self.__tree()
  643.             self.restore_wd(False)
  644.         finally:
  645.             self.END_OPERATION('tree')
  646.             return dir_tree
  647.  
  648.     def __tree(self, __d=None):
  649.         if __d is None:
  650.             __d = {}
  651.             pcardext.cd('/')
  652.  
  653.         for f in pcardext.ls(): # True, '*', False ):
  654.             fname = f[0].lower()
  655.  
  656.             if self.callback is not None:
  657.                 self.callback()
  658.  
  659.             if fname not in ('.', '..'):
  660.                 if f[1] == 'd':
  661.                     self.cd(fname, False)
  662.                     __d[fname] = {}
  663.                     __d[fname] = self.__tree(__d[fname])
  664.                     self.cdup(False)
  665.  
  666.                 else:
  667.                     __d[fname] = f[2]
  668.  
  669.         return __d
  670.  
  671.  
  672.     def get_exif(self, name):
  673.         exif_info = {}
  674.         self.START_OPERATION('get_exif')
  675.         pcf = None
  676.         try:
  677.             pcf = PhotoCardFile(self, name)
  678.             exif_info = exif.process_file(pcf)
  679.         finally:    
  680.             if pcf is not None:
  681.                 pcf.close()
  682.             self.END_OPERATION('get_exif')
  683.             return exif_info
  684.  
  685.  
  686.     def get_exif_path(self, name):
  687.         exif_info = {}
  688.         self.START_OPERATION('get_exif_path')
  689.         self.save_wd()
  690.         try:
  691.             path_list = name.split('/')[:-1]
  692.             filename = name.split('/')[-1]
  693.  
  694.             for p in path_list:
  695.                 self.cd(p, False)
  696.  
  697.             pcf = PhotoCardFile(self, filename)
  698.             exif_info = exif.process_file(pcf)
  699.  
  700.         finally:    
  701.             self.restore_wd(False)
  702.             pcf.close()
  703.             self.END_OPERATION('get_exif_path')
  704.             return exif_info
  705.  
  706.  
  707.  
  708.     def sector(self, sector):
  709.         self.START_OPERATION('sector')
  710.         try:
  711.             data = self._read(sector, 1)
  712.         finally:
  713.             self.END_OPERATION('sector')
  714.             return data
  715.  
  716.     def umount(self):
  717.         pcardext.umount()
  718.         self.pcard_mounted = False
  719.  
  720.     def open_channel(self):
  721.         self.channel_opened = True
  722.         self.device.openPCard()
  723.  
  724.     def close_channel(self):
  725.         self.channel_opened = False
  726.         self.device.closePCard()
  727.  
  728.  
  729.  
  730.  
  731.  
  732.  
  733.